using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace Grammars.Core
{
    public class TokenStream<T>
        where T : Node, new()
    {
        private readonly TextReader stream;
        private readonly Token<T>[] tokens;
        private readonly Regex splitter;

        private string[] currentLine;
        private int currentIndex;

        public TokenStream(Stream stream, Grammar<T> grammar)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");
            if (grammar == null)
                throw new ArgumentNullException("grammar");

            this.stream = new StreamReader(stream);
            this.tokens = grammar.Tokens.ToArray();
            splitter = BuildSplitter();
        }

        private Regex BuildSplitter()
        {
            string regex = string.Format("({0})", string.Join("|", tokens.Select(t => "(" + t.Regex + ")").ToArray()));
            return new Regex(regex, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
        }

        public TokenStream(TextReader reader, Grammar<T> grammar)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");
            if (grammar == null)
                throw new ArgumentNullException("grammar");

            this.stream = reader;
            this.tokens = grammar.Tokens.ToArray();
            splitter = BuildSplitter();
        }


        public TokenStream(string path, Grammar<T> grammar)
        {
            if (path == null)
                throw new ArgumentNullException("path");
            if (grammar == null)
                throw new ArgumentNullException("grammar");

            this.stream = new StreamReader(path);
            this.tokens = grammar.Tokens.ToArray();
        }

        private string NextString()
        {
            while (currentLine == null || currentIndex >= currentLine.Length)
            {
                if (stream.Peek() == -1)
                    return null;

                var line = stream.ReadLine();

                if (line == null)
                    return null;

                // Match a todos los tokens de la linea
                var matches = splitter.Matches(line);

                // Crear el nuevo Array de String
                currentLine = new string[matches.Count];

                // Copiar los matches
                for (int i = 0; i < matches.Count; i++)
                    currentLine[i] = matches[i].Value;

                currentIndex = 0;
            }

            return currentLine[currentIndex++];
        }

        public Token<T> NextToken()
        {
            var str = NextString();

            if (str == null)
                return Token<T>.Eof;

            foreach (var tokenRule in tokens)
            {
                var match = tokenRule.Match(str);

                if (match.Success && match.Value == str)
                {
                    tokenRule.Node = new T { Match = str };
                    tokenRule.ApplyRules();
                    return tokenRule;
                }
            }

            var e = Token<T>.Error;
            e.Node = new T { Error = true };

            return e;
        }
    }
}